﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Core;
using Core.Cache;
using Core.PageEntities;
using Core.SupportEntities;
using Core.Users;
using Core.Store;
using System.Data.Objects;
using System.Data;
using System.Threading;
using System.Data.Common;
using System.Data.Entity.Infrastructure;
using Core.DBClasses;
using Utils;
using Microsoft.Practices.ServiceLocation;
using Core.Logging;
using System.Text.RegularExpressions;

namespace MSSqlDataBase
{
    public class CMSContext : DbContext, IDisposable
    {
        public CMSContext()
        {
            Database.SetInitializer<CMSContext>(null);
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            #region Page Navigation
            modelBuilder.Entity<SitePageGroup>().HasMany(u => u.SitePageDefinition).WithRequired(f => f.SitePageGroup);
            modelBuilder.Entity<SitePageDefinition>().HasMany(u => u.SitePage).WithRequired(f => f.SitePageDefinition).WillCascadeOnDelete();
            modelBuilder.Entity<SitePage>().HasMany(u => u.ContentBlock).WithRequired(f => f.SitePage).WillCascadeOnDelete(); ;
            modelBuilder.Entity<ContentBlockDefinition>().HasMany(u => u.ContentBlock).WithRequired(f => f.ContentBlockDefinition).WillCascadeOnDelete();
            #endregion

            #region Store Navigation
            modelBuilder.Entity<Store_Product>().HasMany(u => u.Store_ProductMainProperty).WithRequired(f => f.Store_Product).WillCascadeOnDelete();
            modelBuilder.Entity<Store_Product>().HasMany(u => u.Store_ProductImage).WithRequired(f => f.Store_Product).WillCascadeOnDelete();
            modelBuilder.Entity<Store_Category>().HasMany(u => u.Store_CategoryMainProperty).WithRequired(f => f.Store_Category).WillCascadeOnDelete();
            modelBuilder.Entity<Culture>().HasMany(u => u.Store_ProductMainProperty).WithRequired(f => f.Culture);
            modelBuilder.Entity<Currency>().HasMany(u => u.Culture).WithRequired(f => f.Currency);            
            modelBuilder.Entity<Store_Property>().HasMany(u => u.Store_PropertyValue).WithRequired(f => f.Store_Property).WillCascadeOnDelete();

            modelBuilder.Entity<Store_Product>().HasMany(u => u.Store_PropertyValue2Product).WithRequired(f => f.Store_Product).WillCascadeOnDelete();
            modelBuilder.Entity<Store_PropertyValue>().HasMany(u => u.Store_PropertyValue2Product).WithRequired(f => f.Store_PropertyValue).WillCascadeOnDelete();
            modelBuilder.Entity<Store_Product>().HasMany(u => u.Store_Category2Product).WithRequired(f => f.Store_Product).WillCascadeOnDelete();
            modelBuilder.Entity<Store_Category>().HasMany(u => u.Store_Category2Product).WithRequired(f => f.Store_Category).WillCascadeOnDelete();
            #endregion
        }

        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges();

            List<Pair<ObjectStateEntry, DBChangesAudit>> listDBChangesAudit = new List<Pair<ObjectStateEntry, DBChangesAudit>>();

            string user;

            if ((Thread.CurrentPrincipal != null) && (Thread.CurrentPrincipal.Identity != null))
                user = Thread.CurrentPrincipal.Identity.Name;
            else
                user = String.Empty;

            ObjectContext objectContext = ((IObjectContextAdapter)this).ObjectContext;
            
            try
            {
                IEnumerable<ObjectStateEntry> EntriesAdded = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added);

                foreach (ObjectStateEntry entry in EntriesAdded)
                {                    
                    foreach (string propertyName in
                                                  GetAllKeyValues(entry).Select(x => x.Key))
                    {
                        CurrentValueRecord current = entry.CurrentValues;
                        string newValue = current.GetValue(
                             current.GetOrdinal(propertyName))
                             .ToString();
                        newValue = ModifyPropery(propertyName, current, newValue);
                    }

                    DBChangesAudit dbChangesAudit = new DBChangesAudit();
                    dbChangesAudit.State = EntityState.Added.ToString();
                    dbChangesAudit.Entity = RegexEntityTypeName(entry.Entity.GetType().Name);
                    dbChangesAudit.Author = user;
                    this.DBChangesAudit.Add(dbChangesAudit);
                    listDBChangesAudit.Add(new Pair<ObjectStateEntry, DBChangesAudit> { First = entry, Second = dbChangesAudit });
                }

                IEnumerable<ObjectStateEntry> EntriesDeleted = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted);

                foreach (ObjectStateEntry entry in EntriesDeleted)
                {
                    DBChangesAudit dbChangesAudit = new DBChangesAudit();
                    dbChangesAudit.State = EntityState.Deleted.ToString();
                    dbChangesAudit.Entity = RegexEntityTypeName(entry.Entity.GetType().Name);
                    dbChangesAudit.EntityId = (int)entry.EntityKey.EntityKeyValues.SingleOrDefault(x => x.Key == "Id").Value;
                    dbChangesAudit.Author = user;
                    this.DBChangesAudit.Add(dbChangesAudit);
                }

                IEnumerable<ObjectStateEntry> EntriesModified = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);

                foreach (ObjectStateEntry entry in EntriesModified)
                {
                    foreach (string propertyName in
                                                  entry.GetModifiedProperties())
                    {
                        DbDataRecord original = entry.OriginalValues;
                        string oldValue = original.GetValue(
                             original.GetOrdinal(propertyName))
                             .ToString();

                        CurrentValueRecord current = entry.CurrentValues;
                        string newValue = current.GetValue(
                             current.GetOrdinal(propertyName))
                             .ToString();

                        if (oldValue != newValue)
                        {
                            DBChangesAudit dbChangesAudit = new DBChangesAudit();
                            dbChangesAudit.State = EntityState.Modified.ToString();
                            dbChangesAudit.Entity = RegexEntityTypeName(entry.Entity.GetType().Name);
                            dbChangesAudit.EntityId = (int)entry.EntityKey.EntityKeyValues.SingleOrDefault(x => x.Key == "Id").Value;
                            dbChangesAudit.Author = user;
                            dbChangesAudit.Property = propertyName;
                            dbChangesAudit.OldValue = oldValue;
                            dbChangesAudit.NewValue = ModifyPropery(propertyName, current, newValue);
                            this.DBChangesAudit.Add(dbChangesAudit);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                ILogger logger = ServiceLocator.Current.GetInstance<ILogger>();             
                logger.LogError(e);
            }

            var returnVal = base.SaveChanges();

            try
            {
                foreach (var pair in listDBChangesAudit)
                {
                    pair.Second.EntityId = (int)pair.First.EntityKey.EntityKeyValues.SingleOrDefault(x => x.Key == "Id").Value;
                }
            }
            catch (Exception e)
            {
                ILogger logger = ServiceLocator.Current.GetInstance<ILogger>();          
                logger.LogError(e);
            }

            base.SaveChanges();

            return returnVal;


        }

        #region Miscellaneous tables
        public DbSet<DBChangesAudit> DBChangesAudit { get; set; }
        #endregion

        #region Culture Tables
        public DbSet<Currency> Currency { get; set; }
        public DbSet<Culture> Culture { get; set; }
        #endregion

        #region Page Tables
        public DbSet<SitePageGroup> SitePageGroup { get; set; }
        public DbSet<SitePageDefinition> SitePageDefinition { get; set; }
        public DbSet<SitePage> SitePage { get; set; }
        public DbSet<ContentBlockDefinition> ContentBlockDefinition { get; set; }
        public DbSet<ContentBlock> ContentBlock { get; set; }
        #endregion

        #region Cache Tables
        public DbSet<CacheTable> CacheTable { get; set; }
        #endregion

        #region User Tables
        public DbSet<UserProfile> UserProfile { get; set; }
        #endregion

        #region Store Tables
        public DbSet<Store_Product> Store_Product { get; set; }
        public DbSet<Store_Category> Store_Category { get; set; }
        public DbSet<Store_Category2Product> Store_Category2Product { get; set; }
        public DbSet<Store_CategoryMainProperty> Store_CategoryMainProperty { get; set; }
        public DbSet<Store_ProductMainProperty> Store_ProductMainProperty_ { get; set; }
        public DbSet<Store_PropertyValue2Product> Store_PropertyValue2Product { get; set; }
        public DbSet<Store_PropertyValue> Store_PropertyValue { get; set; }
        public DbSet<Store_Property> Store_Property { get; set; }
        #endregion

        #region private fields

        //use this method to modify entity property
        private static string ModifyPropery(string propertyName, CurrentValueRecord current, string newValue)
        {

            //if (propertyName == "PropertyName")
            //{
            //    newValue = "new value";
            //    current.SetValue(current.GetOrdinal(propertyName), newValue);
            //}

            return newValue;
        }

        private Dictionary<string, object> GetAllKeyValues(ObjectStateEntry entry)
        {
            var keyValues = new Dictionary<string, object>();
            var currentValues = entry.CurrentValues;
            for (int i = 0; i < currentValues.FieldCount; i++)
            {
                keyValues.Add(currentValues.GetName(i), currentValues.GetValue(i));
            }
            return keyValues;
        }

        private static string RegexEntityTypeName(string proxyTypeName)
        {
            var names = proxyTypeName.Split('_');
            string typeName = proxyTypeName;

            foreach (var name in names)
            {
                Regex rgx = new Regex(@"^[A-Z0-9]*${50,}");
                var match = rgx.Match(name);
                string matchString = match.Value;

                if (String.IsNullOrWhiteSpace(matchString))
                {
                    continue;
                }
                else
                {
                    var stringToReplace = String.Format("_{0}", matchString);
                    typeName = typeName.Replace(stringToReplace, String.Empty);
                    return typeName;
                }
            }

            return typeName;
        }

        #endregion
    }
}
